Skip to content

Conversation

@lvalentine6
Copy link
Member

@lvalentine6 lvalentine6 commented Oct 8, 2025

✨ 개요

SLO에 영향을 미치는 쿼리 성능 개선

🧩 문제 원인

  • EntityGraph + Pageable 조합으로 인해 인메모리 페이징이 발생했습니다.
  • JPA가 전체 데이터를 메모리로 로딩한 후 페이징을 적용하여
    /api/shop, /api/store, /api/cheer API의 응답 지연으로 이어졌습니다.

🛠️ 해결 방법

  • EntityGraph를 유지하면서 DB 레벨에서 페이징을 적용하려면
    쿼리 분리(Query Split) 또는 커스텀 QueryDSL 작성이 필요합니다.
  • 구조적 변경이 크기 때문에, 단기적으로는 BatchSize를 활용하여
    Lazy 로딩 시 발생하는 N+1 문제를 완화하고 SLO 영향을 최소화하는 방향으로 접근했습니다.

📍 적용 범위

  • /api/shop, /api/store, /api/cheer API의 EntityGraph를 제거하고 Batch Fetch를 적용했습니다.
  • 실제 Prod 데이터 분석 결과, 연관 데이터 개수가 대부분 10개 이하로 나타났습니다.
    → 최적 배치 크기는 10이지만, 향후 데이터 증가를 고려해 30으로 설정했습니다.
  • 아래는 배치 크기에 따른 테스트 결과입니다.
구분 배치 미적용 배치 크기 10 배치 크기 30
쿼리 실행 방식 N+1 문제 발생 Batch Fetch 적용 Batch Fetch 적용
총 실행 쿼리 수 41개 5개 5개
평균 레이턴시 약 45ms 약 20.5ms 약 23.5ms
  • dev 환경에 우선 적용 예정이며, 모니터링 후 prod 반영을 검토할 예정입니다.

WAF 브라우저 요청 허용 규칙 개선

  • 허용된 도메인에서 온 요청만 통과하도록 헤더 기반 검증 로직을 강화했습니다.
  • 로컬 → dev 환경 접근이 가능하도록 규칙을 조정했습니다.
  • 클라이언트팀과 함께 테스트를 진행해 정상 접근 가능함을 확인했습니다.

🧾 관련 이슈

#205 #207

🔍 참고 사항

Summary by CodeRabbit

  • Refactor
    • 목록 조회에 페이징(Page)을 적용해 대용량에서 안정적이고 일관된 로딩을 제공합니다.
  • Chores
    • 운영/개발 환경에 Hibernate 배치 페치 크기(30) 설정을 추가하여 배치 로딩 최적화합니다.
    • WAF의 헤더 기반 허용 도메인 규칙을 정교화하고 불필요한 매니지드 봇 규칙을 제거했습니다.
  • Tests
    • 페이징 반영에 맞춰 관련 테스트를 수정했습니다.
  • Documentation
    • 로딩·배치 전략 관련 주석을 보강했습니다.

@coderabbitai
Copy link

coderabbitai bot commented Oct 8, 2025

Walkthrough

페이지네이션 도입과 연관 최적화 주석 추가가 중심이다. 리포지토리/서비스에서 List 반환을 Page로 전환하고 일부 @EntityGraph 제거, Hibernate default_batch_fetch_size를 dev/prod에 추가했으며 테스트를 Page 기반으로 갱신했다. Terraform WAF 규칙은 헤더 기반 바이트 매칭으로 전환됐다.

Changes

Cohort / File(s) Summary of changes
도메인 주석 보강
src/main/java/eatda/domain/cheer/Cheer.java, src/main/java/eatda/domain/store/Store.java
Embedded/BatchSize 한계와 배치 로딩 관련 멀티라인 주석 추가. 동작 변경 없음.
Cheer 리포지토리 페이지네이션
src/main/java/eatda/repository/cheer/CheerRepository.java
반환 타입 ListPage로 변경: findAllByStoreOrderByCreatedAtDesc, findAll(Spec, Pageable), findAllByConditions(default). EntityGraph 제거, query != null 가드로 distinct(true) 적용, Page import 추가.
Store 리포지토리 페이지네이션/Distinct 가드
src/main/java/eatda/repository/store/StoreRepository.java
findAllByConditions, findAll(Spec, Pageable) 반환을 Page<Store>로 변경. EntityGraph 제거 및 import 정리. 태그 조인 시 query != null일 때만 distinct(true) 설정. Page import 추가.
Story 리포지토리 EntityGraph 제거
src/main/java/eatda/repository/story/StoryRepository.java
@EntityGraph 어노테이션(이미지/멤버 관련) 제거 및 import 삭제.
서비스: Page 연동
src/main/java/eatda/service/cheer/CheerService.java, src/main/java/eatda/service/store/StoreService.java
리포지토리 호출을 Page 기반으로 변경하고 .getContent()로 매핑 수행. 외부 메서드 시그니처는 유지.
환경 설정: 배치 페치 사이즈
src/main/resources/application-dev.yml, src/main/resources/application-prod.yml, src/main/resources/application-local.yml
dev/prod에 spring.jpa.properties.hibernate.default_batch_fetch_size: 30 추가. local에는 비교용 주석 추가(기능 변화 없음).
테스트 업데이트(Page 기반)
src/test/java/eatda/repository/cheer/CheerRepositoryTest.java, src/test/java/eatda/repository/store/StoreRepositoryTest.java
테스트를 Page 반환에 맞게 수정(변수/검증을 Page 내용 기준으로 변경). Page import 추가.
인프라: WAF 규칙 전환
terraform/common/waf/main.tf
size_constraint_statement를 다중 byte_match_statement로 대체. origin/referer 헤더에 대해 도메인/로컬호스트 패턴 검사(LOWERCASE 변환 등)로 매칭 로직 변경; AWS Bot Protection 룰 제거.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant StoreService
  participant StoreRepository
  participant DB

  Client->>StoreService: getStores(..., page,size,sort)
  StoreService->>StoreRepository: findAllByConditions(..., PageRequest)
  StoreRepository->>DB: SELECT ... (Specification, JOIN tags, DISTINCT when set)
  DB-->>StoreRepository: Page<Store> (content + metadata)
  StoreRepository-->>StoreService: Page<Store>
  StoreService-->>Client: StoresResponse (from page.content)
Loading
sequenceDiagram
  autonumber
  participant Client
  participant CheerService
  participant CheerRepository
  participant DB

  Client->>CheerService: getCheersByStoreId(storeId, page,size)
  CheerService->>CheerRepository: findAllByStoreOrderByCreatedAtDesc(store, PageRequest)
  CheerRepository->>DB: SELECT ... WHERE store_id=? ORDER BY created_at DESC
  DB-->>CheerRepository: Page<Cheer>
  CheerRepository-->>CheerService: Page<Cheer>
  CheerService-->>Client: List<CheerResponse> (from page.content)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

달빛 아래 깡충, 페이지를 모아 뛰네
리스트 접어두고 배치로 숨 고르며
그래프 한 자락 걷히니 조회는 가벼워
헤더 문지기 새 규칙에 고개 끄덕이고
당근 두 개로 축하의 깡충춤을 추네 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 제목이 SLO 개선을 위해 수행된 주요 쿼리 최적화 작업을 한 문장으로 명확하고 간결하게 요약하고 불필요한 세부 사항이나 모호한 용어가 없어 변경의 핵심이 잘 드러납니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/PRODUCT-285

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Oct 8, 2025

📄 Terraform Plan Summary

🛡️ Common Infrastructure


No plan summary

Status: ✅ No Changes


🛠️ Development Environment


No plan summary

Status: ✅ No Changes


📋 Full Results: View in Actions

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/eatda/repository/store/StoreRepository.java (1)

1-66: @batchsize 어노테이션 누락
엔티티 클래스(Store.cheers 등)의 컬렉션 필드에 @BatchSize(size = 30) 어노테이션을 적용하세요. spring.jpa.properties.hibernate.default_batch_fetch_size=30 설정은 이미 확인되었습니다.

🧹 Nitpick comments (2)
src/main/java/eatda/domain/store/Store.java (1)

60-65: 주석 내용을 현재 설정과 일치하도록 조정해주세요.

application-*.yml에서 spring.jpa.properties.hibernate.default_batch_fetch_size가 30으로 설정되어 있는데, 여기서는 BatchSize=10이라고 설명하고 있어 혼선을 줄 수 있습니다. 실제 동작에 맞춰 주석을 업데이트하거나, Collection 레벨에서 10을 강제하려면 정확한 설정 방법(@batchsize 등)을 주석에 함께 명시해주세요.

src/main/java/eatda/repository/cheer/CheerRepository.java (1)

1-58: Cheer 엔티티 연관관계에 @batchsize 누락
member, store, images 필드에 @batchsize 설정이 없습니다. cheerTags는 Embedded라 현 구조에서 적용이 불가능하므로, 추후 Embedded 제거 후 OneToMany 매핑 전환을 통해 @batchsize를 추가 적용하는 방안을 검토해주세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7539c6 and ab0a79d.

📒 Files selected for processing (13)
  • src/main/java/eatda/domain/cheer/Cheer.java (1 hunks)
  • src/main/java/eatda/domain/store/Store.java (1 hunks)
  • src/main/java/eatda/repository/cheer/CheerRepository.java (3 hunks)
  • src/main/java/eatda/repository/store/StoreRepository.java (3 hunks)
  • src/main/java/eatda/repository/story/StoryRepository.java (0 hunks)
  • src/main/java/eatda/service/cheer/CheerService.java (3 hunks)
  • src/main/java/eatda/service/store/StoreService.java (2 hunks)
  • src/main/resources/application-dev.yml (1 hunks)
  • src/main/resources/application-local.yml (1 hunks)
  • src/main/resources/application-prod.yml (1 hunks)
  • src/test/java/eatda/repository/cheer/CheerRepositoryTest.java (6 hunks)
  • src/test/java/eatda/repository/store/StoreRepositoryTest.java (6 hunks)
  • terraform/common/waf/main.tf (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/eatda/repository/story/StoryRepository.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (7)
src/main/java/eatda/repository/store/StoreRepository.java (3)

11-11: 페이지네이션 지원을 위한 임포트 추가 확인

Page 임포트 추가가 적절합니다. 이는 List<Store>에서 Page<Store>로의 반환 타입 변경을 지원합니다.


54-59: 쿼리 null 체크 추가 확인

query.distinct(true) 호출 전 null 체크를 추가한 것은 좋은 방어적 프로그래밍입니다. JPA Specification에서 count 쿼리 실행 시 query가 null일 수 있으므로, 이 체크는 NullPointerException을 방지합니다.


36-44: 페이지네이션 반환 타입 변경 확인됨
findAllByConditions가 서비스 및 테스트에서 모두 Page<Store>로 사용되고 있어, 의도한 대로 반환 타입 변경이 정상 적용되었습니다.

src/main/java/eatda/repository/cheer/CheerRepository.java (4)

11-11: 페이지네이션 지원을 위한 임포트 추가 확인

Page 임포트 추가가 적절합니다. 이는 반환 타입을 List<Cheer>에서 Page<Cheer>로 변경하는 것을 지원합니다.


38-43: 쿼리 null 체크 추가 확인

query.distinct(true) 호출 전 null 체크를 추가한 것은 StoreRepository와 동일한 패턴으로 일관성 있게 적용되었습니다. 이는 JPA count 쿼리 실행 시 발생할 수 있는 NPE를 방지합니다.


51-51: findAll 메서드 시그니처 변경 확인

findAll(Specification<Cheer>, Pageable) 메서드의 반환 타입을 Page<Cheer>로 변경한 것이 적절합니다.


20-27: 페이지네이션 메서드 호출부 처리 확인 완료

findAllByStoreOrderByCreatedAtDescfindAllByConditions 호출부가 서비스와 테스트에서 모두 Page<Cheer>를 적절히 처리하고 있어 추가 변경이 필요 없습니다.

Copy link
Member

@leegwichan leegwichan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

승로님 고생하셨습니다! 질문 1개 남겼으니 답변 부탁드릴께요!

@github-actions
Copy link

github-actions bot commented Oct 8, 2025

📄 Terraform Plan Summary

🛡️ Common Infrastructure


No plan summary

Status: ✅ No Changes


🛠️ Development Environment


No plan summary

Status: ✅ No Changes


📋 Full Results: View in Actions

Copy link
Member Author

@lvalentine6 lvalentine6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코멘트 남겼습니다 😄
다만.. 어떤 부분을 중점적으로 답변드려야 할지 조금 어려웠는데요.

앞으로는 걱정되는 지점을 조금 더 구체적으로 적어주시면 감사하겠습니다.
예를 들어 null 체크가 불필요하다고 보신 건지, distinct 로직이 궁금하신건지 등..
명확히 말씀해주시면 저도 더 빠르고 정확하게 답변드릴 수 있을 것 같아요 😅

@github-actions
Copy link

github-actions bot commented Oct 8, 2025

📄 Terraform Plan Summary

🛡️ Common Infrastructure


No plan summary

Status: ✅ No Changes


🛠️ Development Environment


No plan summary

Status: ✅ No Changes


📋 Full Results: View in Actions

@sonarqubecloud
Copy link

sonarqubecloud bot commented Oct 8, 2025

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 227dea5 and e87c3f2.

📒 Files selected for processing (1)
  • src/main/java/eatda/domain/store/Store.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test

@lvalentine6 lvalentine6 merged commit c96438f into develop Oct 8, 2025
9 checks passed
@lvalentine6 lvalentine6 deleted the refactor/PRODUCT-285 branch October 8, 2025 14:19
@github-actions
Copy link

github-actions bot commented Oct 8, 2025

🎉 This PR is included in version 1.8.0-develop.17 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions
Copy link

🎉 This PR is included in version 1.9.2 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants